﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.DirectX.DirectInput;

namespace robotGUI
{
    public partial class joyControl : UserControl
    {
        public Device Joystick;
        public System.Timers.Timer joyTimer;
        public System.Timers.Timer keepaliveTimer;
        delegate void updateUIdelegate(int stickX, int stickY, bool button1, bool button2);
        updateUIdelegate updateUI;
        delegate void onJoyChangedDelegate(int X, int Y, bool button1, bool button2);
        onJoyChangedDelegate onJoyChanged;
        delegate void disableJoyUIDelegate();
        disableJoyUIDelegate disableJoyUI;
        public bool EventsEnabled;
        int joyAux1, joyAux2;
        public joyControl()
        {
            InitializeComponent();
            EventsEnabled = false;
            keepaliveTimer = new System.Timers.Timer(200);
            keepaliveTimer.Elapsed += new System.Timers.ElapsedEventHandler(keepaliveTimer_Elapsed);
            joyTimer = new System.Timers.Timer(20);
            joyTimer.Elapsed += new System.Timers.ElapsedEventHandler(pollingTimer_Elapsed);
            updateUI = new updateUIdelegate(UpdateUI);
            onJoyChanged = new onJoyChangedDelegate(OnJoyChanged);
            disableJoyUI = new disableJoyUIDelegate(DisableJoyUI);
            cbxJoySteer.SelectedIndex = Properties.Settings.Default.joySteering;
            cbxJoyThrottle.SelectedIndex = Properties.Settings.Default.joyThrottle;
            if (Properties.Settings.Default.joyAux1 >= cbxJoyAux1.Items.Count)
                cbxJoyAux1.SelectedIndex = joyAux1 = Properties.Settings.Default.joyAux1;
            else
                cbxJoyAux1.SelectedIndex = joyAux1 = 0;
            if (Properties.Settings.Default.joyAux2 >= cbxJoyAux2.Items.Count)
                cbxJoyAux2.SelectedIndex = joyAux2 = Properties.Settings.Default.joyAux2;
            else
                cbxJoyAux2.SelectedIndex = joyAux2 = 0;
            joyDisplay1.DeadBand = deadBand = Properties.Settings.Default.deadBand;
            joystickScan();

        }

        //======================================================================
        // Joystick

        int oldStickX, oldStickY;
        int stickX, stickY;
        int deadBand;
        bool oldButton1, oldButton2;
        bool button1, button2;

        //Combobox list entry contains joystick devices
        class joyListEntry
        {
            public Device joystick;
            public joyListEntry(Device joystick)
            {
                this.joystick = joystick;
            }
            public override string ToString()
            {
                if (joystick == null) return "Joystick Disabled";
                return joystick.Properties.ProductName.Trim();
            }
        }

        //Re-scan for joysticks when user drops down joystick combo
        private void cbxJoystick_DropDown(object sender, EventArgs e)
        {
            joystickScan();
        }

        //Joystick scan routine
        //Fills joystick combo with discovered sticks and selects one
        void joystickScan()
        {
            cbxJoystick.Items.Clear();
            cbxJoystick.Items.Add(new joyListEntry(null));
            //find joystick devices.
            foreach (
                DeviceInstance di in
                Manager.GetDevices(
                    DeviceClass.GameControl,
                    EnumDevicesFlags.AttachedOnly))
            {
                joyListEntry entry = new joyListEntry(new Device(di.InstanceGuid));
                cbxJoystick.Items.Add(entry);
                //Choose previously selected stick
                if (Properties.Settings.Default.joystick != null && di.InstanceGuid == Properties.Settings.Default.joystick)
                    cbxJoystick.SelectedItem = entry;
            }
            //No stick selected? Use first stick.
            if (cbxJoystick.Items.Count > 0 && cbxJoystick.SelectedIndex == -1)
                cbxJoystick.SelectedIndex = 0; //first stick
        }

        //Function pointer class
        class VarRef<T>
        {
            private Func<T> getter;
            public VarRef(Func<T> getter)
            {
                this.getter = getter;
            }
            public T Value { get { return getter(); } }
        }

        VarRef<int>[] joyPos; //value getter array
        //Joystick has been chosen
        private void cbxJoystick_SelectedIndexChanged(object sender, EventArgs e)
        {
            joyTimer.Stop();
            keepaliveTimer.Stop();
            if (Joystick != null) Joystick.Unacquire(); //discard existing joystick
            Joystick = ((joyListEntry)cbxJoystick.SelectedItem).joystick; //retrieve Device
            if (Joystick != null) //Real Device?
            {
                enableJoyControls(true); //Turn controls on
                Properties.Settings.Default.joystick = Joystick.DeviceInformation.InstanceGuid; //save selection
                DeviceCaps joyCaps = Joystick.Caps; //Check capabilities
                createButtonList(cbxJoyAux1, joyCaps.NumberButtons, 1); //buttons for this stick
                createButtonList(cbxJoyAux2, joyCaps.NumberButtons, 2);
                Joystick.Acquire();
                joyTimer.Start(); //Turn on polling timer
                keepaliveTimer.Start(); //Keep alive for failsafe
                joyPos = new VarRef<int>[] //Construct value getter array
                { 
                    new VarRef<int>(()=>0), //None
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.X),
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.Y),
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.Z),
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.Rx),
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.Ry),
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.Rz),
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.GetSlider()[0]),
                    new VarRef<int>(()=>Joystick.CurrentJoystickState.GetSlider()[1])
                };
            }
            else //Something else? All off.
            {
                Properties.Settings.Default.joystick = Guid.Empty;
                enableJoyControls(false);
            }
            oldStickX = 0;
            oldStickY = 0;
            oldButton1 = false;
            oldButton2 = false;
        }

        private int rawToSgn(int raw)
        {
            return (raw - 32768) / 128;
        }

        private void enableJoyControls(bool enabled)
        {
            grpJoyControl.Enabled = enabled;
            joyDisplay1.Enabled = enabled;
            lblAux1.Enabled = enabled;
            lblAux2.Enabled = enabled;
            btnJoyConfig.Enabled = enabled;
        }

        private void createButtonList(ComboBox cbxJoyAux, int numButtons, int defaultButton)
        {
            int selected = cbxJoyAux.SelectedIndex;
            cbxJoyAux.Items.Clear();
            cbxJoyAux.Items.Add("None");
            for (int count = 0; count < numButtons; count++)
                cbxJoyAux.Items.Add("Button " + (count + 1).ToString());
            if (selected < cbxJoyAux.Items.Count) cbxJoyAux.SelectedIndex = selected;
            else cbxJoyAux.SelectedIndex = defaultButton;
        }

        //Callback from joyDisplay's setDeadband event
        private void joyDisplay1_SetDeadband(object sender, SetDeadbandEventArgs e)
        {
            Properties.Settings.Default.deadBand = e.DeadBand;
            deadBand = e.DeadBand;
        }

        //Joystick polling timer
        void pollingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                //Read sticks
                stickX = rawToSgn(joyPos[Properties.Settings.Default.joySteering].Value);//;joystick.CurrentJoystickState.X;
                stickY = rawToSgn(joyPos[Properties.Settings.Default.joyThrottle].Value);
                //Apply deadband
                if (stickX > deadBand) stickX -= deadBand;
                else if (stickX < -deadBand) stickX += deadBand;
                else stickX = 0;
                if (stickY > deadBand) stickY -= deadBand;
                else if (stickY < -deadBand) stickY += deadBand;
                else stickY = 0;
                //Read buttons
                byte[] buttons = Joystick.CurrentJoystickState.GetButtons();
                button1 = joyAux1 == 0 ? false : buttons[joyAux1 - 1] > 0;
                button2 = joyAux2 == 0 ? false : buttons[joyAux2 - 1] > 0;
                //Any movement since last read?
                if (oldStickX != stickX || oldStickY != stickY || oldButton1 != button1 || oldButton2 != button2)
                {
                    keepaliveTimer.Stop();  //Reset keepalive timer
                    keepaliveTimer.Start();
                    this.Invoke(updateUI, new object[] { stickX, stickY, button1, button2 });

                    //Movement trackers
                    oldStickX = stickX;
                    oldStickY = stickY;
                    oldButton1 = button1;
                    oldButton2 = button2;
                }
            }
            catch   //Error during read? Joystick unplugged..
            {
                joyTimer.Stop();
                keepaliveTimer.Stop();
                if (Joystick != null) Joystick.Unacquire();
                if (!this.Disposing && !this.IsDisposed && this.IsHandleCreated) 
                    this.Invoke(disableJoyUI);
            }
        }

        void DisableJoyUI()
        {
            cbxJoystick.SelectedIndex = 0;
            enableJoyControls(false);
            if (TopLevelControl != null) ((frmMain)TopLevelControl).Status = "Joystick Error: Device lost.";
         }

        void UpdateUI(int stickX, int stickY, bool button1, bool button2)
        {
            //Update joyDisplay
            joyDisplay1.X = stickX;
            joyDisplay1.Y = stickY;
            //Update aux display
            if (button1) lblAux1.BackColor = Color.Tomato;
            else lblAux1.BackColor = tableLayoutPanel3.BackColor;
            if (button2) lblAux2.BackColor = Color.Tomato;
            else lblAux2.BackColor = tableLayoutPanel3.BackColor;
            //Event
            OnJoyChanged(stickX, stickY, button1, button2);
        }

        void keepaliveTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (TopLevelControl != null && (!TopLevelControl.Disposing) && (!TopLevelControl.IsDisposed) && TopLevelControl.IsHandleCreated)
                try //ObjectDisposedException??
                {
                    TopLevelControl.Invoke(onJoyChanged, new object[] { oldStickX, oldStickY, oldButton1, oldButton2 });
                }
                catch { }
        }

        public class JoyChangedEventArgs : EventArgs
        {
            public int X;
            public int Y;
            public bool Button1;
            public bool Button2;
            public JoyChangedEventArgs(int x, int y, bool b1, bool b2)
            {
                X = x;
                Y = y;
                Button1 = b1;
                Button2 = b2;
            }
        }

        public delegate void JoyChangedEvent(object sender, JoyChangedEventArgs e);
        public event JoyChangedEvent JoyChanged;
        void OnJoyChanged(int X, int Y, bool Button1, bool Button2)
        {
            if (JoyChanged != null && EventsEnabled)
                JoyChanged(this, new JoyChangedEventArgs(X, Y, Button1, Button2));
        }

        //Combobox properties
        private void cbxJoySteer_SelectedIndexChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.joySteering = cbxJoySteer.SelectedIndex;
        }
        private void cbxJoyThrottle_SelectedIndexChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.joyThrottle = cbxJoyThrottle.SelectedIndex;
        }
        private void cbxJoyAux1_SelectedIndexChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.joyAux1 = joyAux1 = cbxJoyAux1.SelectedIndex;
        }
        private void cbxJoyAux2_SelectedIndexChanged(object sender, EventArgs e)
        {
            Properties.Settings.Default.joyAux2 = joyAux2 = cbxJoyAux2.SelectedIndex;
        }

        //Joystick control panel
        private void btnJoyConfig_Click(object sender, EventArgs e)
        {
            if (Joystick != null) Joystick.RunControlPanel(this);
        }


    }
}
